<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage core
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

use \Iterator;

class Tokenizer implements Iterator {

    private static $tokenMap = array(
        ':' => Token :: T_COLON
        ,';' => Token :: T_SEMICOLON
        ,'(' => Token :: T_PARENTHESIS_OPEN
        ,')' => Token :: T_PARENTHESIS_CLOSE
        ,'{' => Token :: T_CURLY_OPEN
        ,'}' => Token :: T_CURLY_CLOSE
        ,'[' => Token :: T_BRACKET_OPEN
        ,']' => Token :: T_BRACKET_CLOSE
        ,'=' => Token :: T_ASSIGNMENT
        ,',' => Token :: T_COMMA
        ,'"' => Token :: T_DOUBLE_QUOTE
        ,"'" => Token :: T_SINGLE_QUOTE);

    private $tokens = null;

    private $offset = 0;
    private $currentLine = 0;

    private $current = null;

    public function __construct($source) {
        $tokens = token_get_all($source);
        $this->tokens = $tokens;
        $this->rewind();
    }

    public function __destruct() {
        unset($this->tokens);
    }

    /**
     * @return Token
     */
    public function current() {
        return $this->current;
    }

    public function key() {
        return $this->offset;
    }

    public function valid() {
        return $this->offset < count($this->tokens);
    }

    public function next() {
        if($this->offset <= count($this->tokens)) {
            $this->offset++;
        }

        $this->queueCurrent();
    }

    public function rewind() {
        $this->offset = 0;
        $this->currentLine = 0;

        $this->queueCurrent();
    }

    private function queueCurrent() {
        if(!$this->valid()) {
            $this->current = null;
            return;
        }

        $current = $this->tokens[$this->offset];

        if(is_array($current)) {
            $this->currentLine = $current[2] + mb_substr_count($current[1], "\n");
            $this->current = new Token($current[0], $current[1], $current[2]);
        } else {
            if(isset(self :: $tokenMap[$current])) {
                $type = self :: $tokenMap[$current];
            } else {
                $type = Token :: T_OPERATOR;
            }

            $this->current = new Token($type, $current, $this->currentLine);
        }
    }
}


class TokenException extends Exception {

}

class Token {

    // PHP Standard Tokens
    const T_ABSTRACT = T_ABSTRACT;
    const T_AND_EQUAL = T_AND_EQUAL;
    const T_ARRAY = T_ARRAY;
    const T_ARRAY_CAST = T_ARRAY_CAST;
    const T_AS = T_AS;
    const T_BAD_CHARACTER = T_BAD_CHARACTER;
    const T_BOOLEAN_AND = T_BAD_CHARACTER;
    const T_BOOLEAN_OR = T_BOOLEAN_OR;
    const T_BOOL_CAST = T_BOOL_CAST;
    const T_BREAK = T_BREAK;
    const T_CASE = T_CASE;
    const T_CATCH = T_CATCH;
    const T_CHARACTER = T_CHARACTER;
    const T_CLASS = T_CLASS;
    const T_CLONE = T_CLONE;
    const T_CLOSE_TAG =	T_CLOSE_TAG;
    const T_COMMENT = T_COMMENT;
    const T_CONCAT_EQUAL = T_CONCAT_EQUAL;
    const T_CONST = T_CONST;
    const T_CONSTANT_ENCAPSED_STRING = T_CONSTANT_ENCAPSED_STRING;
    const T_CONTINUE = T_CONTINUE;
    const T_CURLY_OPEN = T_CURLY_OPEN;
    const T_DEC = T_DEC;
    const T_DECLARE = T_DECLARE;
    const T_DEFAULT = T_DEFAULT;
    const T_DIV_EQUAL = T_DIV_EQUAL;
    const T_DIR = T_DIR;
    const T_DNUMBER = T_DNUMBER;
    const T_DOC_COMMENT = T_DOC_COMMENT;
    const T_DO = T_DO;
    const T_DOLLAR_OPEN_CURLY_BRACES = T_DOLLAR_OPEN_CURLY_BRACES;
    const T_DOUBLE_ARROW = T_DOUBLE_ARROW;
    const T_DOUBLE_CAST = T_DOUBLE_CAST;
    const T_DOUBLE_COLON = T_DOUBLE_COLON;
    const T_ECHO = T_ECHO;
    const T_ELSE = T_ELSE;
    const T_ELSEIF = T_ELSEIF;
    const T_EMPTY = T_EMPTY;
    const T_ENCAPSED_AND_WHITESPACE = T_ENCAPSED_AND_WHITESPACE;
    const T_ENDDECLARE = T_ENDDECLARE;
    const T_ENDFOR = T_ENDFOR;
    const T_ENDFOREACH = T_ENDFOREACH;
    const T_ENDIF = T_ENDIF;
    const T_ENDSWITCH = T_ENDSWITCH;
    const T_ENDWHILE = T_ENDWHILE;
    const T_END_HEREDOC = T_END_HEREDOC;
    const T_EVAL = T_EVAL;
    const T_EXIT = T_EXIT;
    const T_EXTENDS = T_EXTENDS;
    const T_FILE = T_FILE;
    const T_FINAL = T_FINAL;
    const T_FOR = T_FOR;
    const T_FOREACH = T_FOREACH;
    const T_FUNCTION = T_FUNCTION;
    const T_GOTO = T_GOTO;
    const T_GLOBAL = T_GLOBAL;
    const T_HALT_COMPILER = T_HALT_COMPILER;
    const T_IF = T_IF;
    const T_IMPLEMENTS = T_IMPLEMENTS;
    const T_INC = T_INC;
    const T_INCLUDE = T_INCLUDE;
    const T_INCLUDE_ONCE = T_INCLUDE_ONCE;
    const T_INLINE_HTML = T_INLINE_HTML;
    const T_INSTANCEOF = T_INSTANCEOF;
    const T_INT_CAST = T_INT_CAST;
    const T_INTERFACE = T_INTERFACE;
    const T_ISSET = T_ISSET;
    const T_IS_EQUAL = T_IS_EQUAL;
    const T_IS_GREATER_OR_EQUAL = T_IS_GREATER_OR_EQUAL;
    const T_IS_IDENTICAL = T_IS_IDENTICAL;
    const T_IS_NOT_EQUAL = T_IS_NOT_EQUAL;
    const T_IS_NOT_IDENTICAL = T_IS_NOT_IDENTICAL;
    const T_IS_SMALLER_OR_EQUAL = T_IS_SMALLER_OR_EQUAL;
    const T_LINE = T_LINE;
    const T_LIST = T_LIST;
    const T_LNUMBER = T_LNUMBER;
    const T_LOGICAL_AND = T_LOGICAL_AND;
    const T_LOGICAL_OR = T_LOGICAL_OR;
    const T_LOGICAL_XOR = T_LOGICAL_XOR;
    const T_MINUS_EQUAL = T_MINUS_EQUAL;
    //	const T_ML_COMMENT = T_ML_COMMENT;
    const T_MOD_EQUAL = T_MOD_EQUAL;
    const T_MUL_EQUAL = T_MUL_EQUAL;
    const T_NAMESPACE = T_NAMESPACE;
    const T_NEW = T_NEW;
    const T_NUM_STRING = T_NUM_STRING;
    const T_NS_C = T_NS_C;
    const T_NS_SEPARATOR = T_NS_SEPARATOR;
    const T_OBJECT_CAST = T_OBJECT_CAST;
    const T_OBJECT_OPERATOR = T_OBJECT_OPERATOR;
    //	const T_OLD_FUNCTION = T_OLD_FUNCTION;
    const T_OPEN_TAG = T_OPEN_TAG;
    const T_OPEN_TAG_WITH_ECHO = T_OPEN_TAG_WITH_ECHO;
    const T_OR_EQUAL = T_OR_EQUAL;
    const T_PAAMAYIM_NEKUDOTAYIM = T_PAAMAYIM_NEKUDOTAYIM;
    const T_PLUS_EQUAL = T_PLUS_EQUAL;
    const T_PRINT = T_PRINT;
    const T_PRIVATE = T_PRIVATE;
    const T_PUBLIC = T_PUBLIC;
    const T_PROTECTED = T_PROTECTED;
    const T_REQUIRE = T_REQUIRE;
    const T_REQUIRE_ONCE = T_REQUIRE_ONCE;
    const T_RETURN = T_RETURN;
    const T_SL = T_SL;
    const T_SL_EQUAL = T_SL_EQUAL;
    const T_SR = T_SR;
    const T_SR_EQUAL = T_SR_EQUAL;
    const T_START_HEREDOC = T_START_HEREDOC;
    const T_STATIC = T_STATIC;
    const T_STRING = T_STRING;
    const T_STRING_CAST = T_STRING_CAST;
    const T_STRING_VARNAME = T_STRING_VARNAME;
    const T_SWITCH = T_SWITCH;
    const T_THROW = T_THROW;
    const T_TRY = T_TRY;
    const T_UNSET = T_UNSET;
    const T_UNSET_CAST = T_UNSET_CAST;
    const T_USE = T_USE;
    const T_VAR = T_VAR;
    const T_VARIABLE = T_VARIABLE;
    const T_WHILE = T_WHILE;
    const T_WHITESPACE = T_WHITESPACE;
    const T_XOR_EQUAL = T_XOR_EQUAL;
    const T_FUNC_C = T_FUNC_C;
    const T_CLASS_C = T_CLASS_C;

    // Added Tokens
    const T_BRACKET_OPEN = 1;
    const T_BRACKET_CLOSE = 2;
    const T_CURLY_CLOSE = 3;
    const T_PARENTHESIS_OPEN = 4;
    const T_PARENTHESIS_CLOSE = 5;
    const T_OPERATOR = 6;
    const T_ASSIGNMENT = 7;
    const T_COMMA = 8;
    const T_COLON = 9;
    const T_SEMICOLON = 10;
    const T_DOUBLE_QUOTE = 11;
    const T_SINGLE_QUOTE = 12;

    private $token;
    private $value;
    private $line;

    private static $names = array(
        self :: T_CURLY_CLOSE => 'T_CURLY_CLOSE'
        ,self :: T_PARENTHESIS_OPEN => 'T_PARENTHESIS_OPEN'
        ,self :: T_PARENTHESIS_CLOSE => 'T_PARENTHESIS_CLOSE'
        ,self :: T_CHARACTER => 'T_CHARACTER'
        ,self :: T_ASSIGNMENT => 'T_ASSIGNMENT'
        ,self :: T_OPERATOR => 'T_OPERATOR'
        ,self :: T_COLON => 'T_COLON'
        ,self :: T_SEMICOLON => 'T_SEMICOLON'
        ,self :: T_BRACKET_OPEN => 'T_BRACKET_OPEN'
        ,self :: T_BRACKET_CLOSE => 'T_BRACKET_CLOSE'
        ,self :: T_COMMA => 'T_COMMA'
        ,self :: T_DOUBLE_QUOTE => 'T_DOUBLE_QUOTE'
        ,self :: T_SINGLE_QUOTE => 'T_SINGLE_QUOTE');

    public function __construct($token, $value, $line) {
        $this->token = intval($token);
        $this->value = $value;
        $this->line = $line;
    }

    public function __toString() {
        return $this->line . ' : ' . (self :: getName($this->token)) . '(' . $this->value . ')';
    }

    public function getIdent() {
        return $this->token;
    }

    public function getValue() {
        return $this->value;
    }

    public static function getName($token) {
        $name = token_name($token);
        if($name === 'UNKNOWN' && array_key_exists($token, self :: $names)) {
            $name = self :: $names[$token];
        }
        return $name;
    }
}
